package org.iabako.shared.entity.business;

import com.google.gwt.user.client.rpc.GwtTransient;
import org.hibernate.annotations.Index;
import org.iabako.shared.entity.enumeration.FinancialStatusSale;
import org.iabako.shared.entity.enumeration.PaymentMethod;
import org.iabako.shared.entity.enumeration.PaymentType;
import org.iabako.shared.entity.enumeration.SaleStep;
import org.iabako.shared.entity.user.Enterprise;
import org.iabako.shared.entity.user.User;
import org.iabako.shared.tools.GenericTools;

import javax.persistence.*;
import javax.validation.constraints.Max;
import javax.validation.constraints.NotNull;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

/**
 * Date: 22/09/13
 * Time: 18:21
 */


@Entity
@Table(name = "SALE")
public class Sale extends BusinessEntity implements PaymentsOwner {

    @Id
    @GeneratedValue
    @Column(name = "ID")
    private Long id;

    @Column(name = "NUMBER", nullable = false)
    @Index(name = "NUMBER")
    @NotNull
    private String number;

    @Column(name = "QUOTE", nullable = false)
    @Index(name = "QUOTE")
    @NotNull
    private boolean quote;

    //This is a voluntary soft relation (because not used too much)
    @Column(name = "SALE_QUOTE_ID")
    private String saleQuoteId;

    @OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "sale")
    //To Avoid serializing exception in lazy/proxy objects.
    @GwtTransient
    private List<SaleProductService> productsServices;

    @ManyToOne
    @JoinColumn(name = "ID_CLIENT")
    private Client client;

    @Column(name = "DATE", nullable = false)
    @Index(name = "DATE")
    @Temporal(TemporalType.TIMESTAMP)
    private Date date;

    @Column(name = "COMMENT", length = 1000)
    private String comment;

    @Column(name = "PAYMENT_TYPE", nullable = false)
    @Enumerated(EnumType.STRING)
    @NotNull
    private PaymentType paymentType;

    @Column(name = "PAYMENT_METHOD")
    @Enumerated(EnumType.STRING)
    private PaymentMethod paymentMethod;

    @Column(name = "STATUS", nullable = false)
    @Index(name = "STATUS")
    @Enumerated(EnumType.STRING)
    @NotNull
    private FinancialStatusSale status;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "ID_ENTERPRISE", nullable = false)
    @GwtTransient
    private Enterprise enterprise;

    //For draft mode handling
    @Column(name = "SALE_STEP", nullable = false)
    @Enumerated(EnumType.STRING)
    @NotNull
    private SaleStep saleStep;

    @OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "sale")
    //To Avoid serializing exception in lazy/proxy objects.
    @GwtTransient
    private List<Payment> payments;

    @OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "sale")
    //To Avoid serializing exception in lazy/proxy objects.
    @GwtTransient
    private List<Installment> installments;

    @Column(name = "PROD_SER_AS_STRING", length = 1000)
    private String productsServicesAsString;

    @Column(name = "TAX_PERCENT")
    @Max(100)
    //For performance purpose in Results tables queries and Reports
    private double taxPercent;

    @Column(name = "RECALCULATED_TOTAL")
    //For performance purpose in Reports
    private double recalculatedTotal;

    @Column(name = "DISABLE_TAX")
    private Boolean disableTax;

    @OneToOne(cascade = CascadeType.ALL)
    @JoinColumn(name = "ID_INSTALLMENTS_DEF")
    InstallmentsDefinition installmentsDefinition;

    @OneToOne(cascade = CascadeType.ALL, orphanRemoval = true)
    @JoinColumn(name = "ID_INVOICE_RECIPIENT")
    InvoiceRecipient invoiceRecipient;

    @Column(name = "EXTERNAL_LINK_NAME")
    private String externalLinkName;

    @Column(name = "EXTERNAL_LINK_URL")
    private String externalLinkUrl;

    @Column(name = "CREATION_DATE")
    @Temporal(TemporalType.TIMESTAMP)
    private Date creationDate;

    @Column(name = "LAST_MODIFICATION_DATE")
    @Temporal(TemporalType.TIMESTAMP)
    private Date lastModificationDate;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "ID_LAST_MODIFICATION_USER")
    @GwtTransient
    private User lastModificationUser;

    //For GWT client side use to avoid timezone conversions
    @Transient
    private String dateString;
    @Transient
    private Boolean productServiceWithTax;
    @Transient
    private Boolean productServiceWithDifferentTaxes;

    public Sale() {
        status = FinancialStatusSale._3_OK;
        saleStep = SaleStep.who;
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getNumber() {
        return number;
    }

    public void setNumber(String number) {
        this.number = number;
    }

    public boolean isQuote() {
        return quote;
    }

    public void setQuote(boolean quote) {
        this.quote = quote;
    }

    public String getSaleQuoteId() {
        return saleQuoteId;
    }

    public void setSaleQuoteId(String saleQuoteId) {
        this.saleQuoteId = saleQuoteId;
    }

    public List<SaleProductService> getProductsServices() {
        if (productsServices == null) {
            productsServices = new ArrayList<SaleProductService>();
        }
        return productsServices;
    }

    public void setProductsServices(List<SaleProductService> productsServices) {
        this.productsServices = productsServices;
    }

    public Client getClient() {
        return client;
    }

    public void setClient(Client client) {
        this.client = client;
    }

    public Date getDate() {
        return date;
    }

    public void setDate(Date date) {
        this.date = date;
    }

    public String getDateString() {
        if (dateString == null) {
            dateString = GenericTools.formatDateToString(date);
        }
        return dateString;
    }

    public void setDateString(String dateString) {
        this.dateString = dateString;
    }

    public Enterprise getEnterprise() {
        return enterprise;
    }

    public void setEnterprise(Enterprise enterprise) {
        this.enterprise = enterprise;
    }

    public SaleStep getSaleStep() {
        return saleStep;
    }

    public void setSaleStep(SaleStep saleStep) {
        this.saleStep = saleStep;
    }

    public List<Payment> getPayments() {
        if (payments == null) {
            payments = new ArrayList<Payment>();
        }
        return payments;
    }

    public void setPayments(List<Payment> payments) {
        this.payments = payments;
    }

    public List<Installment> getInstallments() {
        if (installments == null) {
            installments = new ArrayList<Installment>();
        }
        return installments;
    }

    public void setInstallments(List<Installment> installments) {
        this.installments = installments;
    }

    public String getComment() {
        return comment;
    }

    public void setComment(String comment) {
        if (comment != null && comment.length() > 1000) {
            comment = comment.substring(0, 1000);
        }
        this.comment = comment;
    }

    public FinancialStatusSale getStatus() {
        return status;
    }

    public void setStatus(FinancialStatusSale status) {
        this.status = status;
    }

    public PaymentType getPaymentType() {
        return paymentType;
    }

    public void setPaymentType(PaymentType paymentType) {
        this.paymentType = paymentType;
    }

    public PaymentMethod getPaymentMethod() {
        return paymentMethod;
    }

    public void setPaymentMethod(PaymentMethod paymentMethod) {
        this.paymentMethod = paymentMethod;
    }

    public String getProductsServicesAsString() {
        return productsServicesAsString;
    }

    public void setProductsServicesAsString(String productsServicesAsString) {
        this.productsServicesAsString = productsServicesAsString;
    }

    public double getTaxPercent() {
        return taxPercent;
    }

    public void setTaxPercent(double taxPercent) {
        this.taxPercent = taxPercent;
    }

    public double getRecalculatedTotal() {
        return recalculatedTotal;
    }

    public void setRecalculatedTotal(double recalculatedTotal) {
        this.recalculatedTotal = recalculatedTotal;
    }

    public Boolean isDisableTax() {
        return disableTax;
    }

    public void setDisableTax(Boolean disableTax) {
        this.disableTax = disableTax;
    }

    public InstallmentsDefinition getInstallmentsDefinition() {
        return installmentsDefinition;
    }

    public void setInstallmentsDefinition(InstallmentsDefinition installmentsDefinition) {
        this.installmentsDefinition = installmentsDefinition;
    }

    public InvoiceRecipient getInvoiceRecipient() {
        return invoiceRecipient;
    }

    public void setInvoiceRecipient(InvoiceRecipient invoiceRecipient) {
        this.invoiceRecipient = invoiceRecipient;
    }

    public String getExternalLinkName() {
        return externalLinkName;
    }

    public void setExternalLinkName(String externalLinkName) {
        this.externalLinkName = externalLinkName;
    }

    public String getExternalLinkUrl() {
        return externalLinkUrl;
    }

    public void setExternalLinkUrl(String externalLinkUrl) {
        this.externalLinkUrl = externalLinkUrl;
    }

    public Date getCreationDate() {
        return creationDate;
    }

    public void setCreationDate(Date creationDate) {
        this.creationDate = creationDate;
    }

    public Date getLastModificationDate() {
        return lastModificationDate;
    }

    public void setLastModificationDate(Date lastModificationDate) {
        this.lastModificationDate = lastModificationDate;
    }

    public User getLastModificationUser() {
        return lastModificationUser;
    }

    public void setLastModificationUser(User lastModificationUser) {
        this.lastModificationUser = lastModificationUser;
    }

    @Transient
    public void calculateStatus(boolean isDraft) {
        if (isDraft) {
            status = FinancialStatusSale._0_draft;
            return;
        }
        if (GenericTools.isEmpty(getInstallments())) {
            status = FinancialStatusSale._3_OK;
        } else {
            for (Installment installment : getInstallments()) {
                if (installment.getDate().compareTo(new Date()) <= 0) {
                    status = FinancialStatusSale._1_inDebt;
                    return;
                }
            }
            status = FinancialStatusSale._2_withFutureInstallments;
        }
    }

    @Transient
    public double getPonderedTaxPercent() {
        double ponderedAverage = 0;
        double sum = 0;
        for (SaleProductService saleProductService : getProductsServices()) {
            double taxPercent = saleProductService.getTaxPercent() == null ? 0 : saleProductService.getTaxPercent();
            double ponderedElement = saleProductService.getPrice() * saleProductService.getQuantity() * taxPercent;
            sum += saleProductService.getPrice() * saleProductService.getQuantity();

            ponderedAverage += ponderedElement;
        }
        //avoid division by zero
        if (sum == 0) {
            return 0;
        }
        return ponderedAverage / sum;
    }

    @Transient
    public boolean isProductServiceWithTax() {
        if (productServiceWithTax != null) {
            return productServiceWithTax;
        }

        for (SaleProductService saleProductService : getProductsServices()) {
            if ((saleProductService.getTaxPercent() != null)) {
                return (productServiceWithTax = true);
            }
        }
        return (productServiceWithTax = false);
    }

    @Transient
    public boolean isProductServiceWithDifferentTaxes() {
        if (productServiceWithDifferentTaxes != null) {
            return productServiceWithDifferentTaxes;
        }

        Double referenceTax = 0D;
        int count = 0;
        for (SaleProductService saleProductService : getProductsServices()) {
            if (count++ == 0) {
                referenceTax = saleProductService.getTaxPercent();
                referenceTax = referenceTax == null ? 0D : referenceTax;
            }
            Double tax = saleProductService.getTaxPercent();
            tax = tax == null ? 0D : tax;

            if (!tax.equals(referenceTax)) {
                return (productServiceWithDifferentTaxes = true);
            }
        }
        return (productServiceWithDifferentTaxes = false);
    }

    @Transient
    public double calculateAmountFromProductsServices() {
        double sum = 0;
        for (SaleProductService saleProductService : getProductsServices()) {
            sum += saleProductService.getPrice() * saleProductService.getQuantity();
        }
        return sum;
    }

    @Transient
    public double calculateAmountFromPaymentsAndInstalments() {
        double sum = 0;
        for (Payment payment : getPayments()) {
            sum += payment.getAmount();
        }
        for (Installment installment : getInstallments()) {
            sum += installment.getAmount();
        }
        return sum;
    }

    @Transient
    @Override
    public void copyDatesAsString() {
        getDateString();
        return;
    }

    public Sale customClone() {
        Sale sale = new Sale();
        sale.setDate(getDate());
        sale.setDateString(getDateString());
        sale.setComment(getComment());
        sale.setPaymentMethod(getPaymentMethod());
        sale.setPaymentType(getPaymentType());

        InstallmentsDefinition newInstallDef = null;
        if (getInstallmentsDefinition() != null) {
            newInstallDef = new InstallmentsDefinition();
            newInstallDef.setSale(sale);
            for (Installment i : newInstallDef.getInstallments()) {
                addClonedInstallment(i, null, newInstallDef);
            }
        }
        sale.setInstallmentsDefinition(newInstallDef);

        for (SaleProductService ps : productsServices) {
            SaleProductService newProductService = new SaleProductService();
            newProductService.setSale(sale);
            newProductService.setProduct(ps.getProduct());
            newProductService.setService(ps.getService());
            newProductService.setQuantity(ps.getQuantity());
            newProductService.setPrice(ps.getPrice());
            newProductService.setPriceUnit(ps.getPriceUnit());
            newProductService.setTaxPercent(ps.getTaxPercent());
            sale.getProductsServices().add(newProductService);
        }
        for (Installment i : installments) {
            addClonedInstallment(i, sale, null);
        }
        for (Payment p : payments) {
            Payment newPayment = new Payment();
            newPayment.setDate(p.getDate());
            newPayment.setDateString(p.getDateString());
            newPayment.setScheduleDate(p.getScheduleDate());
            newPayment.setScheduleDateString(p.getScheduleDateString());
            newPayment.setAmount(p.getAmount());
            newPayment.setSale(sale);
            sale.getPayments().add(newPayment);
        }
        return sale;
    }

    private void addClonedInstallment(Installment i, Sale sale, InstallmentsDefinition installmentsDefinition) {
        Installment newInstallment = new Installment();
        newInstallment.setDate(i.getDate());
        newInstallment.setDateString(i.getDateString());
        newInstallment.setAmount(i.getAmount());
        newInstallment.setSale(sale);
        newInstallment.setInstallmentsDefinition(installmentsDefinition);

        if (sale == null) {
            installmentsDefinition.getInstallments().add(newInstallment);
        } else {
            sale.getInstallments().add(newInstallment);
        }
    }
}
